﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using SharedLib.Exceptions;
using System.Windows.Forms;
using SharedLib;

namespace SharedLib.MessageFramework
{
    public class MessageDispatcher
    {
        /// <summary>
        /// Controls the thread amount of Handler types
        /// </summary>
        public class HandlerMonitor{
            string _handlerUID;

            public string HandlerUID
            {
                get { return _handlerUID; }
            }

            int _maxCapacity;

            public int MaxCapacity
            {
                get { return _maxCapacity; }
                set { _maxCapacity = value; }
            }

            int _currentUsage = 0;

            public int CurrentUsage
            {
                get { return _currentUsage; }
                set { _currentUsage = value; }
            }

            public HandlerMonitor(string handlerUID, int maxCapacity, int currentUsage)
            {
                _handlerUID = handlerUID;
                _maxCapacity = maxCapacity;
                _currentUsage = currentUsage;
            }

            public bool HasCapacity()
            {
                return (MaxCapacity > CurrentUsage);
            }
        }

        public class HandlerMonitorList : KeyedCollection<string, HandlerMonitor>
        {
            protected override string GetKeyForItem(HandlerMonitor item)
            {
                if (item == null) return null;
                return item.HandlerUID;
            }
        }

        private string log = "";//DEBUG

        private static Object mq_lock = new Object();//message queue lock
        private static Object ht_lock = new Object();//handler thread lock

        private delegate void ProcessMessageDelegate(IAppMessage msg);

        public const int LOOP_INTERVAL = 100;
        public const int LOOP_INTERVAL_QUEUE_PROCESSING = 0;
        /// <summary>
        /// Default handler thead capacity
        /// </summary>
        public const int DEFAULT_THREAD_CAPACITY = 2;
        /// <summary>
        /// Capacity of the threads available for each handler
        /// </summary>
        private HandlerMonitorList _threadCapacity = new HandlerMonitorList();

        protected virtual void OnMessageProcessing(IAppMessage msg) { }
        protected virtual void OnMessageProcessed(IAppMessage msg) { }

        public MessageDispatcher()
        {
            threadMessageQueue = new Thread(new ThreadStart(CheckMessageQueue));
            threadMessageQueue.Start();
        }

        public void SetHandlerCapacity(string typeHandlerUID, int capacity)
        {
            lock (ht_lock)
            {
                if (!_threadCapacity.Contains(typeHandlerUID))
                {
                    _threadCapacity.Add(new HandlerMonitor(typeHandlerUID, capacity, 0));
                }
                else
                {
                    _threadCapacity[typeHandlerUID].MaxCapacity = capacity;
                }
            }
        }

        private bool CheckOutHandler(string typeHandlerUID)
        {
            bool rtn = false;
            lock (ht_lock)
            {
                if (!_threadCapacity.Contains(typeHandlerUID))
                {
                    _threadCapacity.Add(new HandlerMonitor(typeHandlerUID, DEFAULT_THREAD_CAPACITY, 1));
                    rtn = true;
                }
                else
                {
                    if (_threadCapacity[typeHandlerUID].HasCapacity())
                    {
                        _threadCapacity[typeHandlerUID].CurrentUsage++;
                        rtn = true;
                    }
                }
            }
            return rtn;
        }

        private void CheckInHandler(string typeHandlerUID)
        {
            lock (ht_lock)
            {
                if (!_threadCapacity.Contains(typeHandlerUID))
                {
                    _threadCapacity.Add(new HandlerMonitor(typeHandlerUID, DEFAULT_THREAD_CAPACITY, 0));
                }
                else
                {
                    _threadCapacity[typeHandlerUID].CurrentUsage--;
                }
            }
        }

        public virtual bool ExitUntilQueueCleared
        {
            get { return true; }
        }



        private long _killThreadFlag = 0;
        /// <summary>
        /// Thread-safe on-off switch flag
        /// </summary>
        protected bool KillThreadFlag
        {
            get { 
                return (Interlocked.Read(ref _killThreadFlag) == 1); 
            }
            set {
                if (value)
                {
                    Interlocked.Exchange(ref _killThreadFlag, 1);
                }
                else
                {
                    Interlocked.Exchange(ref _killThreadFlag, 0);
                }
            }
        }
        /// <summary>
        /// Queue thread
        /// </summary>
        protected Thread threadMessageQueue;

        private ConcurrentQueue<IAppMessage> _messageQueue = new ConcurrentQueue<IAppMessage>();
        
        protected virtual ConcurrentQueue<IAppMessage> MessageQueue
        {
            get { return _messageQueue; }
        }

        public virtual void EnqueueMessage(IAppMessage msg)
        {
            //lock (mq_lock)
            //{
                MessageQueue.Enqueue(msg);
                //log += "Enqueued\n";
            //}
            //CallOnQueueModified(this, new TracyGeneralEventArgs(MessageQueue.Count));
        }

        protected bool PrepareHandler(IAppMessage msg)
        {
            if (msg != null && msg.Handler != null)
            {
                if (CheckOutHandler(msg.HandlerUID))
                {
                    return true;
                }
                else
                {
                    MessageQueue.Enqueue(msg);
                }
            }
            //TODO: Exception
            return false;
        }

        protected virtual void PerformCallback(IAppMessage msg)
        {
            if (msg != null)
            {
                if (msg.CallBack != null)
                {

                    if (msg.Sender is Control)
                    {
                        Control senderControl = msg.Sender as Control;
                        if (senderControl.InvokeRequired)
                        {
                            senderControl.Invoke(msg.CallBack, msg);//Invoke on UI thread (TODO: let UI itself to handle this)
                        }
                    }
                    else
                    {
                        msg.CallBack.Invoke(msg);
                    }
                }
            }
        }


        protected virtual void ProcessMessage(IAppMessage msg)
        {
            if (msg != null && msg.Handler != null)
            {
                try
                {
                    object tmpInstance = Activator.CreateInstance(msg.Handler);
                    if (tmpInstance != null && tmpInstance is IAppMessageHandler)
                    {
                        IAppMessageHandler handler = (IAppMessageHandler)tmpInstance;

                        OnMessageProcessing(msg);
                        handler.ProcessMessage(msg);
                        OnMessageProcessed(msg);

                        if (msg.Exception != null)
                        {
                            OnException.InvokeEvent(this, new ExceptionEventArgs(msg.Exception));
                        }
                        PerformCallback(msg);
                    }
                    else
                    {
                        //TODO:Exception
                    }
                }
                catch (Exception ex)
                {
                    EventHandler<ExceptionEventArgs> eventVar = OnException;
                    if (eventVar != null)
                    {
                        eventVar(this, new ExceptionEventArgs(ex));
                    }
                    else
                    {
                        throw ex;
                    }
                }
                finally
                {
                    CheckInHandler(msg.HandlerUID);
                }
            }
            //TODO: Exception
        }

        /// <summary>
        /// (Standalone Thread) Check Message Queue and dispatch messages
        /// </summary>
        protected virtual void CheckMessageQueue()
        {
            bool queueChangeFlag = false;
            IAppMessage msg;
            while (!KillThreadFlag)
            {
                queueChangeFlag = false;
                while (MessageQueue.Count > 0 && (ExitUntilQueueCleared || !KillThreadFlag))
                {
                    //log += "FoundOne\n";
                    //queueChangeFlag = false;
                    msg = null;
                    //lock (mq_lock)
                    //{
                    if (MessageQueue.Count > 0)
                    {
                        if (MessageQueue.TryDequeue(out msg))
                        {
                            //log += "Dequeued\n";
                            if (msg != null)
                            {
                                if (PrepareHandler(msg))//handler thread capacity checking
                                {
                                    ProcessMessageDelegate asyncDelegate = new ProcessMessageDelegate(this.ProcessMessage);
                                    asyncDelegate.BeginInvoke(msg, null, null);
                                    queueChangeFlag = true;
                                }
                                //CallOnMessageProcessed(this, new TracyMessageEventArgs(msg));
                            }
                            else
                            {
                                throw new Exception("Message is null");//TODO: Exception Framework
                            }
                        }
                    }
                    //}
                    Thread.Sleep(LOOP_INTERVAL_QUEUE_PROCESSING);
                }
                if (queueChangeFlag)
                {
                    //CallOnQueueModified(this, new TracyGeneralEventArgs(MessageQueue.Count));
                }
                Thread.Sleep(LOOP_INTERVAL);
            }
        }

        public void Stop()
        {
            KillThreadFlag = true;
            IAppMessage msg;
            while (MessageQueue.Count > 0)
            {
                if (MessageQueue.TryDequeue(out msg))
                {
                    msg.CancelFlag = true;
                }
            }
        }

        public event EventHandler<ExceptionEventArgs> OnException;
    }
}
